/*	Copyright (c) 2017 Jean-Marc VIGLINO, 
  released under the CeCILL-B license (French BSD license)
  (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt).
*/

import ol_ext_inherits from '../util/ext'
import ol_geom_Polygon from 'ol/geom/Polygon'
import ol_geom_MultiPolygon from 'ol/geom/MultiPolygon'
import ol_geom_LinearRing from 'ol/geom/LinearRing'
import ol_interaction_Draw from 'ol/interaction/Draw'
import ol_interaction_Select from 'ol/interaction/Select'

/** Interaction to draw holes in a polygon.
 * It fires a drawstart, drawend event when drawing the hole
 * and a modifystart, modifyend event before and after inserting the hole in the feature geometry.
 * @constructor
 * @extends {ol_interaction_Interaction}
 * @fires drawstart
 * @fires drawend
 * @fires modifystart
 * @fires modifyend
 * @param {olx.interaction.DrawHoleOptions} options extend olx.interaction.DrawOptions
 * 	@param {Array<ol.layer.Vector> | function | undefined} options.layers A list of layers from which polygons should be selected. Alternatively, a filter function can be provided. default: all visible layers
 * 	@param { ol.style.Style | Array<ol.style.Style> | StyleFunction | undefined }	Style for the selected features, default: default edit style
 */
var ol_interaction_DrawHole = function(options) {
  if (!options) options = {};
  var self = this;

  // Select interaction for the current feature
  this._select = new ol_interaction_Select({ style: options.style });
  this._select.setActive(false);

  // Geometry function that test points inside the current
  var geometryFn, geomFn = options.geometryFunction;
  if (geomFn) {
    geometryFn = function(c,g) {
      g = self._geometryFn (c, g);
      return geomFn (c,g);
    }
  } else {
    geometryFn = function(c,g) { return self._geometryFn (c, g); }
  }

  // Create draw interaction
  options.type = "Polygon";
  options.geometryFunction = geometryFn;
  ol_interaction_Draw.call(this, options);

  // Layer filter function
  if (options.layers) {
    if (typeof (options.layers) === 'function') {
      this.layers_ = options.layers;
    } else if (options.layers.indexOf) {
      this.layers_ = function(l) {
        return (options.layers.indexOf(l) >= 0); 
      };
    }
  }

  // Start drawing if inside a feature
  this.on('drawstart', this._startDrawing.bind(this));
  // End drawing add the hole to the current Polygon
  this.on('drawend', this._finishDrawing.bind(this));
};
ol_ext_inherits(ol_interaction_DrawHole, ol_interaction_Draw);

/**
 * Remove the interaction from its current map, if any,  and attach it to a new
 * map, if any. Pass `null` to just remove the interaction from the current map.
 * @param {ol.Map} map Map.
 * @api stable
 */
ol_interaction_DrawHole.prototype.setMap = function(map) {
  if (this.getMap()) this.getMap().removeInteraction(this._select);
  if (map) map.addInteraction(this._select);
  ol_interaction_Draw.prototype.setMap.call (this, map);
};

/**
 * Activate/deactivate the interaction
 * @param {boolean}
 * @api stable
 */
ol_interaction_DrawHole.prototype.setActive = function(b) {
  this._select.getFeatures().clear();
  ol_interaction_Draw.prototype.setActive.call (this, b);
};

/**
 * Remove last point of the feature currently being drawn 
 * (test if points to remove before).
 */
ol_interaction_DrawHole.prototype.removeLastPoint = function() {
  if (this._feature && this._feature.getGeometry().getCoordinates()[0].length>2) {
    ol_interaction_Draw.prototype.removeLastPoint.call(this);
  }
};

/** 
 * Get the current polygon to hole
 * @return {ol.Feature}
 */
ol_interaction_DrawHole.prototype.getPolygon = function() {
  return this._polygon;
  // return this._select.getFeatures().item(0).getGeometry();
};

/**
 * Get current feature to add a hole and start drawing
 * @param {ol_interaction_Draw.Event} e
 * @private
 */
ol_interaction_DrawHole.prototype._startDrawing = function(e) {
  var map = this.getMap();
  var layersFilter = this.layers_;
  this._feature = e.feature;
  var coord = e.feature.getGeometry().getCoordinates()[0][0];
  // Check object under the pointer
  var features = map.getFeaturesAtPixel(
    map.getPixelFromCoordinate(coord), {
      layerFilter: layersFilter
    }
  );
  this._current = null;
  if (features) {
    for (var k=0; k<features.length; k++) {
      var poly = features[k].getGeometry();
      if (poly.getType() === "Polygon"
        && poly.intersectsCoordinate(coord)) {
        this._polygonIndex = false;
        this._polygon = poly;
        this._current = features[k];
      }
      else if (poly.getType() === "MultiPolygon"
        && poly.intersectsCoordinate(coord)) {
        for (var i=0, p; p=poly.getPolygon(i); i++) {
          if (p.intersectsCoordinate(coord)) {
            this._polygonIndex = i;
            this._polygon = p;
            this._current = features[k];
            break;
          }
        }
      }
      if (this._current) break;
    }
  }
  this._select.getFeatures().clear();
  if (!this._current) {
    this.setActive(false);
    this.setActive(true);
  } else {
    this._select.getFeatures().push(this._current);
  }
};

/**
 * Stop drawing and add the sketch feature to the target feature. 
 * @param {ol_interaction_Draw.Event} e
 * @private
 */
ol_interaction_DrawHole.prototype._finishDrawing = function(e) {
  // The feature is the hole
  e.hole = e.feature;
  // Get the current feature
  e.feature = this._select.getFeatures().item(0);
  this.dispatchEvent({ type: 'modifystart', features: [ this._current ] });
  // Create the hole
  var c = e.hole.getGeometry().getCoordinates()[0];
  if (c.length > 3) {
    if (this._polygonIndex!==false) {
      var geom = e.feature.getGeometry();
      var newGeom = new ol_geom_MultiPolygon([]);
      for (var i=0, pi; pi=geom.getPolygon(i); i++) {
        if (i===this._polygonIndex) {
          pi.appendLinearRing(new ol_geom_LinearRing(c));
          newGeom.appendPolygon(pi);
        } else {
          newGeom.appendPolygon(pi);
        }
      }
      e.feature.setGeometry(newGeom);
    } else {
      this.getPolygon().appendLinearRing(new ol_geom_LinearRing(c));
    }
  }
  this.dispatchEvent({ type: 'modifyend', features: [ this._current ] });
  // reset
  this._feature = null;
  this._select.getFeatures().clear();
};

/**
 * Function that is called when a geometry's coordinates are updated.
 * @param {Array<ol.coordinate>} coordinates
 * @param {ol_geom_Polygon} geometry
 * @return {ol_geom_Polygon}
 * @private
 */
ol_interaction_DrawHole.prototype._geometryFn = function(coordinates, geometry) {
  var coord = coordinates[0].pop();
  if (!this.getPolygon() || this.getPolygon().intersectsCoordinate(coord)) {
    this.lastOKCoord = [coord[0],coord[1]];
  }
  coordinates[0].push([this.lastOKCoord[0],this.lastOKCoord[1]]);

  if (geometry) {
    geometry.setCoordinates([coordinates[0].concat([coordinates[0][0]])]);
  } else {
    geometry = new ol_geom_Polygon(coordinates);
  }
  return geometry;
};

export default ol_interaction_DrawHole
